<template>
  <div class="recall-test-container">
    <!-- 左侧容器：检索设置 -->
    <div class="left-panel">
      <div class="panel-header">
        <h3 class="panel-title">检索设置</h3>
      </div>
      <div class="panel-content">
        <a-card
                title="通用型混合检索模式"
                :bordered="false"
                :headStyle="{ height: '20px', backgroundColor: '#eaecf0' }"
                :style="{ border: '1px solid blue' }"
                style="width: 100%;"
            >
                
                <div class="setting-section">
                    <div class="setting-title" style="text-align: left; margin-bottom: 12px;">检索模式</div>
                    <a-radio-group 
                        v-model:value="searchMode" 
                        button-style="solid" 
                        style="display: flex; width: 100%; border: 1px solid #d9d9d9; border-radius: 4px; overflow: hidden;"
                    >
                        <a-radio-button value="weight" style="flex: 1; text-align: center; border-right: 1px solid #d9d9d9; border-top: none; border-bottom: none; border-left: none;">
                            权重模式
                        </a-radio-button>
                        <a-radio-button value="rerank" style="flex: 1; text-align: center; border-right: 1px solid #d9d9d9; border-top: none; border-bottom: none; border-left: none;">
                            Rerank模式
                        </a-radio-button>
                        <a-radio-button value="vector" style="flex: 1; text-align: center; border-right: 1px solid #d9d9d9; border-top: none; border-bottom: none; border-left: none;">
                            向量模式
                        </a-radio-button>
                        <a-radio-button value="fulltext" style="flex: 1; text-align: center; border-right: 1px solid #d9d9d9; border-top: none; border-bottom: none; border-left: none;">
                            全文检索
                        </a-radio-button>
                        <a-radio-button value="keyword" style="flex: 1; text-align: center; border: none;">
                            关键字检索
                        </a-radio-button>
                    </a-radio-group>
                </div>
                
                <a-card 
                    :bordered="false" 
                    :headStyle="{ height: '20px', backgroundColor: '#eaecf0' }"
                    :style="{ marginTop: '16px', border: '1px solid #d9d9d9' }"
                    style="width: 100%;"
                >
                    <div style="display: flex; gap: 16px;">
                        <div style="flex: 1;">
                            <div class="setting-title" style="text-align: left; margin-bottom: 8px;">TopK</div>
                            <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px;">
                                <a-input-number 
                                    v-model:value="retrievalCount" 
                                    :min="1" 
                                    :max="50" 
                                    style="width: 80px;" 
                                    @change="handleTopKChange"
                                />
                                <a-slider 
                                    v-model:value="retrievalCount" 
                                    :min="1" 
                                    :max="50" 
                                    style="flex: 1;" 
                                    @change="handleTopKChange"
                                />
                            </div>
                            <div style="font-size: 12px; color: rgba(0, 0, 0, 0.45);">
                                返回的最大结果数量
                            </div>
                        </div>
                        <div style="flex: 1;">
                            <div class="setting-title" style="text-align: left; margin-bottom: 8px;">Score阈值</div>
                            <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px;">
                                <a-input-number 
                                    v-model:value="similarityThresholdPercent" 
                                    :min="0" 
                                    :max="100" 
                                    :formatter="value => `${value}%`"
                                    :parser="value => value.replace('%', '')"
                                    style="width: 80px;" 
                                    @change="handleThresholdPercentChange"
                                />
                                <a-slider 
                                    v-model:value="similarityThreshold" 
                                    :min="0" 
                                    :max="1" 
                                    :step="0.01" 
                                    style="flex: 1;" 
                                    @change="handleThresholdChange"
                                />
                            </div>
                            <div style="font-size: 12px; color: rgba(0, 0, 0, 0.45); display: flex; justify-content: space-between;">
                                <span>低相关性</span>
                                <span>高相关性</span>
                            </div>
                        </div>
                    </div>
                </a-card>
                
                <!-- 权重设置卡片 -->
                <a-card 
                    v-if="searchMode === 'weight'"
                    :bordered="false" 
                    :headStyle="{ height: '20px', backgroundColor: '#eaecf0' }"
                    :style="{ marginTop: '16px', border: '1px solid #d9d9d9' }"
                    style="width: 100%;"
                >
                    <div class="setting-title" style="text-align: left; margin-bottom: 12px;">检索权重设置</div>
                    <div style="display: flex; flex-direction: column; gap: 16px;">
                        <div>
                            <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
                                <span>语义搜索权重</span>
                                <span>{{ semanticWeight }}%</span>
                            </div>
                            <div style="display: flex; align-items: center; gap: 8px;">
                                <a-slider 
                                    v-model:value="semanticWeight" 
                                    :min="0" 
                                    :max="100" 
                                    style="flex: 1;" 
                                    @change="handleSemanticWeightChange"
                                />
                                <a-input-number 
                                    v-model:value="semanticWeight" 
                                    :min="0" 
                                    :max="100" 
                                    style="width: 70px;" 
                                    @change="handleSemanticWeightChange"
                                />
                            </div>
                        </div>
                        
                        <div>
                            <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
                                <span>关键词搜索权重</span>
                                <span>{{ keywordWeight }}%</span>
                            </div>
                            <div style="display: flex; align-items: center; gap: 8px;">
                                <a-slider 
                                    v-model:value="keywordWeight" 
                                    :min="0" 
                                    :max="100" 
                                    style="flex: 1;"
                                    :disabled="true"
                                />
                                <a-input-number 
                                    v-model:value="keywordWeight" 
                                    :min="0" 
                                    :max="100" 
                                    style="width: 70px;"
                                    :disabled="true"
                                />
                            </div>
                        </div>
                        
                        <div style="font-size: 12px; background-color: #f5f5f5; padding: 8px; border-radius: 4px; color: rgba(0, 0, 0, 0.65);">
                            语义搜索和关键词搜索的权重总和始终为100%。调整一个权重，另一个将自动计算。
                        </div>
                    </div>
                </a-card>
            </a-card>
            
            <!-- 模型设置卡片 -->
            <a-card
                v-if="searchMode !== 'weight'"
                title="模型设置"
                :bordered="false"
                :headStyle="{ height: '20px', backgroundColor: '#eaecf0' }"
                :style="{ border: '1px solid blue', marginTop: '16px' }"
                style="width: 100%;"
            >
                
                <div class="setting-section">
                    <div class="setting-title" style="text-align: left; margin-bottom: 12px;" v-if="searchMode == 'vector'">Embedding模型</div>
                    <a-select 
                        v-model:value="embeddingModel" 
                        style="width: 100%;"
                        placeholder="选择Embedding模型"
                        v-if="searchMode == 'vector'"
                        :options="embeddingModelOptions"
                    >
                    </a-select>
                    <div style="font-size: 12px; color: rgba(0, 0, 0, 0.45); margin-top: 4px;" v-if="searchMode == 'vector'">
                        用于将文本转换为向量的模型
                    </div>
                </div>
                
                <div v-if="searchMode === 'rerank'|| searchMode === 'vector' || searchMode === 'fulltext' || searchMode === 'keyword'" class="setting-section" style="margin-top: 20px;">
                    <div class="setting-title" style="text-align: left; margin-bottom: 12px;">Rerank模型</div>
                    <a-select 
                        v-model:value="rerankModel" 
                        style="width: 100%;"
                        placeholder="选择Rerank模型"
                        :options="[{ value: '', label: '不使用Rerank' }, ...rerankModelOptions]"
                    >
                    </a-select>
                    <div style="font-size: 12px; color: rgba(0, 0, 0, 0.45); margin-top: 4px;">
                        用于重排检索结果的模型，仅在Rerank模式和全文检索模式下生效
                    </div>
                </div>
            </a-card>
      </div>
    </div>
    
    <!-- 中间容器 -->
    <div class="middle-panel">
      <!-- 上方容器：召回测试 -->
      <div class="test-panel">
        <div class="panel-header">
          <h3 class="panel-title">召回测试</h3>
        </div>
        <div class="test-content">
          <a-textarea 
            v-model:value="queryText" 
            placeholder="请输入要测试的文本查询..." 
            :auto-size="{ minRows: 4, maxRows: 8 }"
            class="query-input"
          />
          <div class="action-bar">
            <a-button type="primary" @click="performRecall" :loading="loading">
              开始测试
            </a-button>
          </div>
          
          <!-- 历史记录部分 -->
          <div class="history-section">
            <div class="history-header">
              <div class="header-title">历史记录</div>
            </div>
            
            <div class="history-list" v-if="historyRecords.length > 0">
              <div 
                v-for="(item, index) in historyRecords" 
                :key="index" 
                class="history-item"
                @click="loadHistoryRecord(item)"
              >
                <div class="history-item-content">
                  <div class="history-query">{{ item.query }}</div>
                  <div class="history-meta">
                    <span>{{ item.time }}</span>
                    <span>{{ item.source || '本地知识库' }}</span>
                  </div>
                </div>
              </div>
            </div>
            
            <div class="empty-history" v-else>
              <a-empty description="暂无历史记录" />
            </div>
          </div>
        </div>
      </div>
    </div>
    
    <!-- 右侧容器：召回展示 -->
    <div class="right-panel">
      <div class="panel-header">
        <h3 class="panel-title">召回结果</h3>
        <div class="result-meta" v-if="recallResults.length > 0">
          共召回 {{ recallResults.length }} 条内容
        </div>
      </div>
      <div class="result-container">
        <div v-if="recallResults.length > 0" class="result-list">
          <div v-for="(result, index) in recallResults" :key="index" class="result-item">
            <div class="result-header">
              <div class="result-title">{{ result.title }}</div>
              <div class="result-score">
                <a-tag :color="getScoreColor(result.score)">{{ (result.score * 100).toFixed(2) }}%</a-tag>
              </div>
            </div>
            <div class="result-content">{{ result.content }}</div>
            <div class="result-footer">
              <div class="result-source">来源：{{ result.source }}</div>
              <div class="result-section">段落：{{ result.section }}</div>
            </div>
          </div>
        </div>
        <div v-else-if="loading" class="loading-results">
          <a-spin tip="正在召回中...">
          </a-spin>
        </div>
        <div v-else class="empty-results">
          <inbox-outlined style="font-size: 48px; color: #d9d9d9; margin-bottom: 16px;" />
          <p>暂无召回结果，请输入查询内容进行测试</p>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive, computed, onMounted, watch, onUnmounted } from 'vue'
import { 
  SaveOutlined, 
  DeleteOutlined, 
  ExperimentOutlined, 
  ClearOutlined,
  InboxOutlined,
  InfoCircleOutlined
} from '@ant-design/icons-vue'
import { message } from 'ant-design-vue'
import http from '@/utils/ai/http'

// 检索设置
const retrievalModel = ref('bge-large-zh')
const retrievalCount = ref(2)
const similarityThreshold = ref(0.2)
const similarityThresholdPercent = ref(20)
const retrievalMode = ref('hybrid')
const hybridMode = ref('weight') // 默认为权重模式
const searchMode = ref('weight') // 新增：检索模式，可选值：weight(权重模式)、rerank(rerank模式)、vector(向量模式)、fulltext(全文检索模式)

// 滑块标记
const sliderMarks = {
  1: '1',
  5: '5',
  10: '10',
  15: '15',
  20: '20'
}

const thresholdMarks = {
  0: '0',
  0.5: '0.5',
  1: '1'
}

// 查询文本
const queryText = ref('')
const loading = ref(false)

// 历史记录
const historyRecords = ref([])

// 添加获取召回历史的函数
const fetchRecallHistory = async () => {
  try {
    // 从当前URL中获取knowledge_id
    const currentPath = window.location.pathname
    const knowledgeId = currentPath.split('/documents/')[1]
    
    if (!knowledgeId) {
      message.error('未找到知识库ID')
      return
    }

    // 获取用户信息
    const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}')
    if (!userInfo.userId || !userInfo.username) {
      message.error('未找到用户信息')
      return
    }

    // 调用获取召回历史接口
    const res = await http.get(`/knowledge_api/${knowledgeId}/recall_test_history`, {
      params: {
        username: userInfo.username,
        user_id: userInfo.userId
      }
    })

    if (res.data.code === 200) {
      // 更新历史记录列表
      historyRecords.value = res.data.data.data.map(item => ({
        query: item.content,
        time: new Date(item.created_at * 1000).toLocaleString(),
        source: item.source || '本地知识库',
        count: 0 // 这里可以根据实际需求添加召回数量
      }))
    } else {
      historyRecords.value = []
    }
  } catch (error) {
    console.error('获取召回历史失败:', error)
    historyRecords.value = []
  }
}

// 召回结果
const recallResults = ref([])

// 执行召回
const performRecall = async () => {
  if (!queryText.value.trim()) {
    return
  }
  
  loading.value = true
  
  try {
    // 从当前URL中获取knowledge_id
    const currentPath = window.location.pathname
    const knowledgeId = currentPath.split('/documents/')[1]
    
    if (!knowledgeId) {
      message.error('未找到知识库ID')
      return
    }

    // 获取用户信息
    const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}')
    if (!userInfo.userId || !userInfo.username) {
      message.error('未找到用户信息')
      return
    }

    // 构建基础请求参数
    const params = {
      username: userInfo.username,
      user_id: userInfo.userId,
      query: queryText.value,
      top_k: retrievalCount.value,
      score_threshold: similarityThreshold.value
    }

    // 根据检索模式设置 search_method
    if (searchMode.value === 'fulltext') {
      params.search_method = 'full_text_search'
    } else if (searchMode.value === 'keyword') {
      params.search_method = 'keyword_search'
    } else if (searchMode.value === 'vector') {
      params.search_method = 'semantic_search'
    } else if (searchMode.value === 'rerank' || searchMode.value === 'weight') {
      params.search_method = 'hybrid_search'
    }

    // 如果是权重模式，添加权重参数
    if (searchMode.value === 'weight') {
      params.vector_weight = semanticWeight.value / 100
      params.keyword_weight = keywordWeight.value / 100
    }

    // 如果是 Rerank 模式且选择了 Rerank 模型，添加 Rerank 参数
    if (searchMode.value === 'rerank' && rerankModel.value && rerankModel.value !== '') {
      // 从模型名称中提取提供商名称
      const modelInfo = rerankModelOptions.value.find(opt => opt.value === rerankModel.value)
      if (modelInfo) {
        // 从完整的模型列表中查找对应的提供商
        const fullModelInfo = rerankModelList.value.find(provider => 
          provider.models.some(model => model.model === rerankModel.value)
        )
        if (fullModelInfo) {
          params.reranking_mode = 'reranking_model'
          params.reranking_model_name = rerankModel.value
          params.reranking_provider_name = fullModelInfo.provider
        }
      }
    }

    // 调用召回接口
    const res = await http.post(`/knowledge_api/${knowledgeId}/retrieve`, params)

    if (res.data.code === 200) {
      // 更新召回结果
      recallResults.value = res.data.data.records.map(item => ({
        title: item.segment.document.name || '无标题',
        content: item.segment.content,
        source: item.segment.document.name || '本地知识库',
        section: `第${item.segment.position}段`,
        score: item.score || 0
      }))
    
    // 添加到历史记录
    historyRecords.value.unshift({
      query: queryText.value,
      time: new Date().toLocaleString(),
      count: recallResults.value.length,
      source: '本地知识库'
    })
    } else {
      message.error(res.data.message || '召回失败')
    }
  } catch (error) {
    console.error('召回失败:', error)
    message.error('召回失败')
  } finally {
    loading.value = false
  }
}

// 清空查询文本
const clearQuery = () => {
  queryText.value = ''
}

// 加载历史记录
const loadHistoryRecord = (item) => {
  queryText.value = item.query
  performRecall()
}

// 根据分数获取标签颜色
const getScoreColor = (score) => {
  if (score >= 0.8) return 'success'
  if (score >= 0.6) return 'processing'
  if (score >= 0.4) return 'warning'
  return 'default'
}

// 处理TopK变化
const handleTopKChange = (value) => {
  retrievalCount.value = value
  // 可以在这里添加TopK与阈值的关联逻辑
  // 例如：当TopK增大时，可以适当降低阈值
  if (value > 20 && similarityThreshold.value > 0.8) {
    similarityThreshold.value = 0.8
    similarityThresholdPercent.value = 80
  }
}

// 处理阈值变化（从滑块）
const handleThresholdChange = (value) => {
  similarityThreshold.value = value
  // 更新百分比形式的值
  similarityThresholdPercent.value = Math.round(value * 100)
}

// 处理阈值百分比变化（从输入框）
const handleThresholdPercentChange = (value) => {
  similarityThresholdPercent.value = value
  // 更新小数形式的值
  similarityThreshold.value = value / 100
}

// 权重设置
const semanticWeight = ref(30)
const keywordWeight = ref(70)

// 处理语义权重变化
const handleSemanticWeightChange = (value) => {
  semanticWeight.value = value
  // 自动计算关键词权重
  keywordWeight.value = 100 - value
}

// 模型设置
const embeddingModel = ref('bge-large-zh')
const rerankModel = ref('')
const initialRetrievalCount = ref(50)
const rerankModelOptions = ref([])
const embeddingModelOptions = ref([])
const rerankModelList = ref([]) // 添加完整的 Rerank 模型列表

// 获取 embedding 模型列表
const fetchEmbeddingModels = async () => {
  try {
    const res = await http.get('/knowledge_base/txt_embedding_model')
    if (res.data.code === 200) {
      const options = []
      res.data.data.forEach(provider => {
        provider.models.forEach(model => {
          options.push({
            value: model.model,
            label: `${model.model}【${provider.label.zh_Hans}】`
          })
        })
      })
      embeddingModelOptions.value = options
      
      // 设置默认值
      if (options.length > 0) {
        // 尝试从 localStorage 获取保存的值
        const savedEmbeddingModel = localStorage.getItem('embeddingModel')
        if (savedEmbeddingModel && options.some(opt => opt.value === savedEmbeddingModel)) {
          embeddingModel.value = savedEmbeddingModel
        } else {
          embeddingModel.value = options[0].value
          // 保存默认值
          localStorage.setItem('embeddingModel', options[0].value)
        }
      }
    } else {
      message.error(res.data.message || '获取 embedding 模型列表失败')
    }
  } catch (error) {
    console.error('获取 embedding 模型列表失败:', error)
    message.error('获取 embedding 模型列表失败')
  }
}

// 监听 embedding 模型变化
watch(embeddingModel, (newValue) => {
  if (newValue) {
    localStorage.setItem('embeddingModel', newValue)
  }
})

// 获取 rerank 模型列表
const fetchRerankModels = async () => {
  try {
    const res = await http.get('/knowledge_base/rerank_model')
    if (res.data.code === 200) {
      // 保存完整的模型列表
      rerankModelList.value = res.data.data
      
      const options = []
      res.data.data.forEach(provider => {
        provider.models.forEach(model => {
          options.push({
            value: model.model,
            label: `${model.model}【${provider.label.zh_Hans}】`
          })
        })
      })
      rerankModelOptions.value = options
      
      // 设置默认值为空（不使用Rerank）
      rerankModel.value = ''
    } else {
      message.error(res.data.message || '获取 rerank 模型列表失败')
    }
  } catch (error) {
    console.error('获取 rerank 模型列表失败:', error)
    message.error('获取 rerank 模型列表失败')
  }
}

// 修改 onMounted 钩子
onMounted(() => {
  fetchRecallHistory()
  fetchRerankModels()
  fetchEmbeddingModels()
})
</script>

<style scoped>
.recall-test-container {
  margin-top: 0.5%;
  margin-left: 0.5%;
  margin-right: 0.5%;
  display: flex;
  height: 81vh;
  background-color: #f0f2f5;
  gap: 12px;
}

.left-panel,
.middle-panel,
.right-panel {
  background: white;
  border-radius: 8px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  max-height: 81vh;
}

.left-panel {
  width: 30%;
  min-width: 300px;
}

.middle-panel {
  width: 30%;
  display: flex;
  flex-direction: column;
}

.right-panel {
  width: 40%;
}

.panel-header {
  padding: 16px;
  border-bottom: 1px solid #f0f0f0;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.panel-title {
  font-size: 16px;
  font-weight: 500;
  margin: 0;
  color: rgba(0, 0, 0, 0.85);
}

.panel-content {
  flex: 1;
  padding: 16px;
  overflow-y: auto;
}

/* 左侧检索设置样式 */
.setting-section {
  margin-bottom: 20px;
}

.setting-title {
  margin-bottom: 8px;
  font-weight: 500;
  color: rgba(0, 0, 0, 0.85);
}

.save-settings-btn {
  margin-top: 16px;
}

/* 中间面板样式 */
.test-panel {
  height: 100%;
  display: flex;
  flex-direction: column;
}

.test-content {
  padding: 16px;
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow-y: hidden;
}

.query-input {
  margin-bottom: 16px;
  border-radius: 4px;
}

.action-bar {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  margin-bottom: 20px;
}

/* 历史记录样式 */
.history-section {
  margin-top: 20px;
  border-top: 1px solid #f0f0f0;
  padding-top: 16px;
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

.history-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}

.header-title {
  font-size: 16px;
  font-weight: 500;
  color: rgba(0, 0, 0, 0.85);
}

.history-list {
  max-height: none;
  overflow-y: auto;
  margin-bottom: 12px;
  flex: 1;
  border: 1px solid #e8e8e8;
  border-radius: 4px;
  padding: 4px;
}

.history-item {
  padding: 10px 12px;
  border-bottom: 1px solid #f0f0f0;
  cursor: pointer;
  transition: all 0.3s;
  background-color: #fafafa;
  border-radius: 4px;
  margin-bottom: 8px;
  border: 1px solid #e9e9e9;
}

.history-item:last-child {
  margin-bottom: 0;
}

.history-item:hover {
  background-color: #f0f7ff;
}

.history-item-content {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.history-query {
  font-weight: 500;
  color: rgba(0, 0, 0, 0.85);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 60%;
}

.history-meta {
  display: flex;
  gap: 16px;
  font-size: 12px;
  color: rgba(0, 0, 0, 0.45);
  white-space: nowrap;
}

.empty-history {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100px;
  color: rgba(0, 0, 0, 0.45);
}

/* 右侧结果展示样式 */
.result-container {
  flex: 1;
  overflow-y: auto;
  padding: 16px;
}

.result-meta {
  font-size: 14px;
  color: rgba(0, 0, 0, 0.45);
}

.result-list {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.result-item {
  background-color: #f5f9ff;
  border-radius: 8px;
  padding: 16px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
  border: 1px solid #e6f4ff;
}

.result-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}

.result-title {
  font-weight: 600;
  color: rgba(0, 0, 0, 0.85);
}

.result-content {
  margin-bottom: 12px;
  line-height: 1.6;
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
}

.result-footer {
  display: flex;
  justify-content: space-between;
  font-size: 12px;
  color: rgba(0, 0, 0, 0.45);
}

.empty-results,
.loading-results {
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  color: rgba(0, 0, 0, 0.45);
  text-align: center;
  padding: 40px 0;
}

.loading-content {
  display: flex;
  flex-direction: column;
  align-items: center;
}
</style> 